package com.amadeus.spin.standard.util;

import javax.xml.transform.TransformerException;

import org.apache.log4j.Logger;
import org.apache.regexp.RE;
import org.apache.regexp.RESyntaxException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.amadeus.spin.devtool.soap.shooter.FatalException;

/**
 * Class to test root, values and nodes in an XML Document. Document is an
 * instance variable that has to be set by the client. Each test method returns
 * a boolean, and sets a m_wrongValue attibute, which can later on be logged by
 * caller. Uses XPath to lookup values in the Document, so the first argument of
 * testExists() and testValue() can be any XPath expression.
 */
public class XMLTester {
  private static Logger m_log = Logger.getLogger(XMLTester.class);

  private Document document;

  // contains the value found by XMLTester if not correct
  private String m_wrongValue;

  public void setDOM(Document dom) {
    document = dom;
  }

  /**
   * Gets wrong value in case of error (only for testRoot and testValue)
   */
  public String getWrongValue() {
    if (m_wrongValue == null) {
      return "null";
    }
    return m_wrongValue;
  }

  /**
   * Tests if root has right value
   */
  public boolean testRoot(String value) {
    NodeList list = document.getChildNodes();
    Node rootNode = list.item(0);
    String root = rootNode.getNodeName();
    if (!root.equals(value)) {
      m_wrongValue = rootNode.getNodeName();
      return false;
    }
    return true;
  }

  /**
   * Checks XPath is reachable in m_document
   *
   * @param XPath
   *          The xpath to fetch
   * @return true if the xpath is reachable, false otherwise
   * @throws FatalException
   * @throws TransformerException
   */
  public boolean testExists(String XPath) {
    return XPathHelper.selectNode(document, XPath) != null;
  }

  /**
   * Checks XPath is unreachable in m_document
   *
   * @param XPath
   *          The xpath to fetch
   * @return true if the xpath is not reachable, false otherwise
   */
  public boolean testNotExists(String XPath) {
    return !testExists(XPath);
  }

  public String getXPathString(String xpath) {
    return XPathHelper.selectNodeValue(document, xpath);
  }

  /**
   * Checks XPath is reachable in m_document and contains a certain value
   *
   * @param XPath
   *          The xpath to fetch decribing a leaf
   * @param value
   *          The value the leaf must have
   * @return true if the xpath is reachable and that the leaf value is value,
   *         false otherwise
   */
  public boolean testValue(String XPath, String value) {
    String nodeValue = getXPathString(XPath);
    if (nodeValue != null) {
      if (nodeValue.equals(value)) {
        return true;
      }
      m_wrongValue = nodeValue;
      return false;
    }
    // did not find text node with right value
    m_wrongValue = "NODE_NOT_FOUND";
    return false;
  }

  /**
   * Checks XPath is reachable in m_document and does not contain a certain
   * value
   * 
   * @param xpath
   *          The xpath to fetch decribing a leaf
   * @param value
   *          The value the leaf must have
   * @return true if the xpath is reachable and that the leaf value is not
   *         value, false otherwise
   */
  public boolean testNotExistsWithValue(String xpath, String value) {
    NodeList nodes = XPathHelper.selectNodeList(document, xpath);
    if (nodes != null) {
      if (checkTextNode(value, nodes)) {
        return false;
      }
    }
    return true;
  }

  private boolean checkTextNode(String value, NodeList list) {
    for (int i = 0; i < list.getLength(); i++) {
      Node node = list.item(i);
      if (node.getNodeType() == Node.TEXT_NODE || node.getNodeType() == Node.CDATA_SECTION_NODE) {
        String nodeValue = node.getNodeValue();
        if (nodeValue.equals(value)) {
          return true;
        }
      }
      else if (node.getNodeType() == Node.ELEMENT_NODE) {
        if (checkTextNode(value, node.getChildNodes())) {
          return true;
        }
      }
    }
    return false;
  }

  /**
   * Checks XPath is reachable in m_document and its value matches a certain
   * regexp
   *
   * @param XPath
   *          The xpath to fetch decribing a leaf
   * @param pattern
   *          The regexp to match
   * @return true if the xpath is reachable and that the leaf value matches the
   *         pattern, false otherwise
   */
  public boolean testRegexp(String XPath, String pattern) {
    try {
      Node node = XPathHelper.selectNode(document, XPath);
      if (node == null) {
        return false;
      }
      // node is found
      RE re = new RE(pattern);

      NodeList list = node.getChildNodes();
      for (int i = 0; i < list.getLength(); i++) {
        Node node2 = list.item(i);
        if (node2.getNodeType() == Node.TEXT_NODE || node2.getNodeType() == Node.CDATA_SECTION_NODE) {
          String nodeValue = node2.getNodeValue();
          m_wrongValue = nodeValue;
          return re.match(nodeValue);
        }
      }
      m_wrongValue = "NODE_NOT_FOUND";
      return false;
    }
    catch (RESyntaxException re) {
      m_log.error(re.getMessage());
      return false;
    }
  }
}
